using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Threading;
using DataBuddy;

namespace Astro.Core
{
    public partial class User : IAstroUser, IMenuItem
    {
        #region Custom Field Support

        //container for custom field values
        private NameValueCollection _nvc = new NameValueCollection();
        private bool _isCustomReady = false;

        /// <summary>
        /// Returns a custom fields keys and values
        /// </summary>
        /// <returns></returns>
        public NameValueCollection CustomFields()
        {
            if (!_isCustomReady)
                DeserializeCustomFields();
            return _nvc;
        }

        /// <summary>
        /// Called Pre-Update to set the PropertyKeys and PropertyValues properties
        /// </summary>
        internal void SerializeCustomFields()
        {
            StringBuilder sbKey = new StringBuilder();
            StringBuilder sbValue = new StringBuilder();

            int index = 0;
            foreach (string key in _nvc.AllKeys)
            {
                if (key.IndexOf(':') != -1)
                    throw new ArgumentException("Extened Fields Key can not contain the character \":\"");

                string v = _nvc[key];
                if (!string.IsNullOrEmpty(v))
                {
                    sbKey.AppendFormat("{0}:S:{1}:{2}:", key, index, v.Length);
                    sbValue.Append(v);
                    index += v.Length;
                }
            }


            PropertyKeys = sbKey.ToString();
            PropertyValues = sbValue.ToString();
        }

        /// <summary>
        /// Called during Loaded() method to copy values from PropertyKeys and PropertyValues 
        /// to the CustomFields NameValueCollection
        /// </summary>
        internal void DeserializeCustomFields()
        {
            _nvc.Clear();

            if (PropertyKeys != null && PropertyValues != null)
            {
                char[] splitter = new char[1] { ':' };
                string[] keyNames = PropertyKeys.Split(splitter);

                for (int i = 0; i < (keyNames.Length / 4); i++)
                {
                    int start = int.Parse(keyNames[(i * 4) + 2], CultureInfo.InvariantCulture);
                    int len = int.Parse(keyNames[(i * 4) + 3], CultureInfo.InvariantCulture);
                    string key = keyNames[i * 4];

                    if (((keyNames[(i * 4) + 1] == "S") && (start >= 0)) && (len > 0) && (PropertyValues.Length >= (start + len)))
                    {
                        _nvc[key] = PropertyValues.Substring(start, len);
                    }
                }
            }

            _isCustomReady = true;
        }

        /// <summary>
        /// Provides access to the custom fields collection
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public string this[string key]
        {
            get { return _nvc[key]; }
            set { _nvc[key] = value; }
        }

        /// <summary>
        /// Returns the custom field value for the given key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        /// <example>
        /// #foreach($post in $posts)
        /// [TAB]The value of my custom field is $post.Custom("MyCustomFieldName")
        /// #end
        /// </example>
        public string Custom(string key)
        {
            return this[key];
        }

        #endregion

        private string[] _roles = null;
        public string[] Roles
        {
            get
            {
                if(_roles == null)
                {
                    UserRoleCollection urc = UserRoleCollection.FetchByColumn(UserRole.Columns.UserId, Id);
                    List<string> roles = new List<string>(urc.Count);
                    foreach(UserRole ur in urc)
                        roles.Add(ur.RoleName);

                    _roles = roles.ToArray();
                }

                return _roles;
            }
        }

        private string _loadedPassword = null;

        protected override void BeforeValidate()
        {
            if (UniqueId == Guid.Empty)
                UniqueId = Guid.NewGuid();
            
            if (string.IsNullOrEmpty(Password))
                throw new Exception("Invalid Password");

            SerializeCustomFields();

            if (_loadedPassword != Password)
            {
                PasswordFormat = 1;
                PasswordSalt = GenerateSalt();
                Password = EncodePassword(Password, PasswordSalt);
            }


            if (string.IsNullOrEmpty(ProperName))
                ProperName = Name;

        }

        protected override void Loaded()
        {
            //use this to check for password updates
            _loadedPassword = Password;
            DeserializeCustomFields();

            base.Loaded();
        }

        protected override void BeforeUpdate()
        {
            Events.Instance().ExecuteUserBeforeUserUpdate(this);
            base.BeforeUpdate();
        }

        protected override void AfterUpdate()
        {
            Events.Instance().ExecuteAfterUserUpdated(this);
            base.AfterUpdate();
        }

        private static string GenerateSalt()
        {
            byte[] buf = new byte[16];
            (new RNGCryptoServiceProvider()).GetBytes(buf);
            return Convert.ToBase64String(buf);
        }

        internal static string EncodePassword(string pass, string salt)
        {
            byte[] bIn = Encoding.Unicode.GetBytes(pass);
            byte[] bSalt = Convert.FromBase64String(salt);
            byte[] bAll = new byte[bSalt.Length + bIn.Length];

            Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
            Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);

            return Convert.ToBase64String(HashAlgorithm.Create("MD5").ComputeHash(bAll));
        }

        #region IMenuItem Members

        public List<MenuItem> GetMenuItems()
        {
            List<MenuItem> menuItems = new List<MenuItem>();

            menuItems.Add(new MenuItem("Name", "$user.Name", "The username", "User"));
            menuItems.Add(new MenuItem("ProperName", "$user.ProperName", "A user\'s formal name. If null, it will be the Name", "User"));
            menuItems.Add(new MenuItem("Email", "$user.Email", "", "User"));
            menuItems.Add(new MenuItem("TimeZoneOffSet", "$user.TimeZoneOffSet", "", "User"));
            menuItems.Add(new MenuItem("WebSite", "$user.WebSite", "", "User"));
            menuItems.Add(new MenuItem("Bio", "$user.Bio", "", "User"));

            return menuItems;
        }

        #endregion
    }
}